using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.ComponentModel;
using System.Runtime.Serialization;

namespace XMas
{
    /// <summary>
    /// Basic blend list node, can blend in a variable numbers of item, each one with
    /// different blend values
    /// </summary>
    [Serializable()]
    public class AnimNode_BlendList : AnimationNode
    {
        /// <summary>
        /// Items blending time in seconds
        /// </summary>
        protected List<float> _BlendInTimesList;
        /// <summary>
        /// Total time fro blending
        /// </summary>
        protected double _BlendTotalTime;
        /// <summary>
        /// Current blending time
        /// </summary>
        protected double _BlendCurrentTime;
        /// <summary>
        /// Index of blending child 
        /// </summary>
        protected int    _BlendNodeIdx;
        /// <summary>
        /// Number of version of this subnode, used in serialization
        /// </summary>
        /// <remarks>
        /// If you add a new value to serialization, you need to increment this float
        /// This is need to have compatibility with olders version of library
        /// <see cref="WriteXml(System.Xml.XmlWriter)"/>
        /// </remarks> 
        protected const float  _SerializationVersionChild = 1.0f;


        #region Properties

        /// <summary>
        /// Atribute for _BlendInTimesList
        /// <see cref="AnimNode_BlendList._BlendInTimesList"/>
        /// </summary>
        [CategoryAttribute("BlendList"), DescriptionAttribute("Items blending time in seconds")]
        public List<float> BlendInTimesList
        {
            get { return _BlendInTimesList;  }
            set { _BlendInTimesList = value; }
        }
        
        #endregion

        
        # region Protected Members

        /// <summary>
        /// Initialization of node, set initial values
        /// </summary>
        /// <param name="ID">Node ID</param>
        protected override void Initialize(System.Guid ID)
        {
            _BlendInTimesList = new List<float>();
            base.Initialize(ID);            

            _strName = "Blend List";
            AddChild("Element 0", 1.0f);
            _fCurrentValue = 0;
            _fEditorValue = 0;
            _BlendTotalTime = 0;
            _BlendCurrentTime = 0;
            _BlendNodeIdx = -1;
            _CategoryName = "Control Nodes";
        }

        /// <summary>
        /// Activate one child and calculate blending
        /// </summary>
        /// <param name="idxChild"></param>
        protected void SetActiveChild(int idxChild)
        {
            if (idxChild >= 0 && idxChild < _animNodeItemList.Count)
            {
                int iOldValue = (int)_fCurrentValue;
                if (iOldValue != idxChild)
                {
                    // Set blends values
                    _BlendTotalTime = 0;
                    if (idxChild < _BlendInTimesList.Count)
                        _BlendTotalTime = (double)_BlendInTimesList[idxChild];
                    _BlendCurrentTime = 0;
                    _BlendNodeIdx = iOldValue;

                    _fCurrentValue = idxChild;
                }
            }
        }

        #endregion


        # region Public Members

        /// <summary>
        /// Add new child to this node
        /// </summary>
        /// <param name="ItemText">Child text</param>
        /// <param name="fInitialWeigth">Child initial weigth </param>
        /// <returns>New item</returns>
        public override AnimationNodeItem AddChild(string ItemText, float fInitialWeigth)
        {
            AnimationNodeItem NodeItem = base.AddChild(ItemText, fInitialWeigth);
            if (NodeItem != null && _BlendInTimesList.Count < _animNodeItemList.Count)
                _BlendInTimesList.Add(0.2f);

            return NodeItem;
        }

        /// <summary>
        /// Remove a child from this node
        /// </summary>
        /// <param name="iIdxNode">Child index </param>
        /// <returns>True if the children has been removed</returns>
        public override bool RemoveChild(int iIdxNode)
        {
            bool bResult = base.RemoveChild(iIdxNode);
            if (bResult && iIdxNode < _BlendInTimesList.Count)
                _BlendInTimesList.RemoveAt(iIdxNode);

            return bResult;
        }

        /// <summary>
        /// EDITOR EVENT: Called by editor when slider move
        /// </summary>
        /// <param name="iNewValue">Value is between 0 and 100</param>
        public override void HandleSliderMove(int iNewValue)
        {
            // int value is between 0 and 100
            _fEditorValue = -1.0f;
            if (_animNodeItemList.Count > 0)
            {
                float fMid = 101.0f / _animNodeItemList.Count;
                int iSelected = (int)(iNewValue / fMid);
                _fEditorValue = (float)iSelected;
            }
        }

        /// <summary>
        /// Internal get current value, first try to use reflexion. If no reflexion found use editor value
        /// Override with caution
        /// </summary>
        /// <returns>Value from 0 to 1</returns>
        protected override float GetValue()
        {
            // We need a value from 0 to ChildCount -1
            int iResultValue = (int)base.GetValue();
            // Clamp
            if (iResultValue < 0)
                iResultValue = 0;
            if (iResultValue >= _animNodeItemList.Count)
                iResultValue = _animNodeItemList.Count - 1;

            return (float)iResultValue;
        }

        /// <summary>
        /// Get the value to set in eatch frame (animations) or initial load
        /// </summary>
        /// <returns>Value from 0 to 100</returns>
        public override int GetEditorValue()
        {
            int iValue = (int)_fEditorValue;
            if (_animNodeItemList.Count > 1)
            {
                iValue *= ((100 / (_animNodeItemList.Count - 1)) + 1);
                if (iValue > 100)
                    iValue = 100;
            }
            return iValue; 
        }

        /// <summary>
        /// Used by system when the node need to update his weight
        /// </summary>
        /// <param name="DeltaSeconds">Elapsed time</param>
        public override void UpdateWeights(double DeltaSeconds)
        {
            int iNewValue = (int)GetValue();
            int iOldValue = (int)_fCurrentValue;

            if (iNewValue != iOldValue)
                SetActiveChild(iNewValue);
            else
            {
                if (_BlendNodeIdx != -1)
                {
                    _BlendCurrentTime += DeltaSeconds;
                    if (_BlendCurrentTime > _BlendTotalTime)
                        _BlendNodeIdx = -1;
                }
            }

            float fCurrentWeigth = 1.0f;
            float fBlendWeigth = 0;
            if (_BlendNodeIdx != -1 && _BlendTotalTime > 0)
            {
                fCurrentWeigth = (float)(_BlendCurrentTime / _BlendTotalTime);
                fBlendWeigth = 1.0f - fCurrentWeigth;
            }

            int iCurrentValue = (int)_fCurrentValue;            
            for (int iIdx = 0; iIdx < _animNodeItemList.Count; iIdx++)
            {
                if (iIdx == iCurrentValue)
                    _animNodeItemList[iIdx]._fLocalWeigth = fCurrentWeigth;
                else
                {
                    if (iIdx == _BlendNodeIdx)
                        _animNodeItemList[iIdx]._fLocalWeigth = fBlendWeigth;
                    else
                        _animNodeItemList[iIdx]._fLocalWeigth = 0;
                }
            }
        }

        /// <summary>
        /// Used by system after the node update his weight
        /// </summary>
        /// <param name="elapsedTime"></param>
        public override void PostUpdateWeights(TimeSpan elapsedTime)
        {
        }

        #endregion


        #region Load/Save XML

        /// <summary>
        /// Function to write in XML format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the save order, it must be the same that read order!!
        /// </remarks>
        /// <param name="writer">Writer to store data</param>
        public override void WriteXml(XmlWriter writer)
        {
            base.WriteXml(writer);

            writer.WriteElementString("SerializationVersionChild", _SerializationVersionChild.ToString(new System.Globalization.CultureInfo("en-US")));
            writer.WriteStartElement("BlendList", null);
            // Item list
            foreach (float fBlend in _BlendInTimesList)
            {
                writer.WriteStartElement("Blend", null);
                writer.WriteElementString("Time", fBlend.ToString(new System.Globalization.CultureInfo("en-US")));
                writer.WriteEndElement();
            }
            writer.WriteEndElement();     
        }

        /// <summary>
        /// Function to read xml format, if you overwrite you must call base class first
        /// </summary>
        /// <remarks>
        /// Be carefully with the read order, it must be the same that save order!!
        /// </remarks>
        /// <param name="reader">Reader to retrieve data</param>
        public override void ReadXml(XmlReader reader)
        {
            base.ReadXml(reader);

            float SerializationVersionChild = reader.ReadElementContentAsFloat("SerializationVersionChild", reader.NamespaceURI);
            // Get blend list
            _BlendInTimesList = new List<float>();
            reader.ReadStartElement("BlendList");
            bool bFound = false;
            while (reader.IsStartElement("Blend", reader.NamespaceURI))
            {
                reader.ReadStartElement();
                float fTime = reader.ReadElementContentAsFloat("Time", reader.NamespaceURI);
                reader.ReadEndElement();
                _BlendInTimesList.Add(fTime);
                bFound = true;
            }
            // </BlendList> only if items found
            if (bFound)
                reader.ReadEndElement();
        }

        #endregion


        /// <summary>
        /// Constructor
        /// </summary>
        public AnimNode_BlendList()
        {            
        }
    }
}
